package com.ali.common;

import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECPoint;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.util.HashMap;
import java.util.Map;

public class Sm2Util {
    private static final String STDNAME_WAPIP = "wapip192v1";
    private static final String STDNAME_WAPI = "wapi192v1";
    private static final String STDNAME_SM2P = "sm2p256v1";
    private static final String ALGORITHM_EC = "EC";
    private static final SM2Engine.Mode MODE_C1C2C3 = SM2Engine.Mode.C1C2C3;
    private static final SM2Engine.Mode MODE_C1C3C2 = SM2Engine.Mode.C1C3C2;

    /**
     * 获取公钥与私钥
     */
    public static Map<String, String> getKey() {
        Map<String, String> keyMap = new HashMap<>();

        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM_EC, new BouncyCastleProvider());
            ECGenParameterSpec ecGenParameterSpec = new ECGenParameterSpec(STDNAME_SM2P);
            keyPairGenerator.initialize(ecGenParameterSpec, new SecureRandom());
            KeyPair keyPair = keyPairGenerator.generateKeyPair();

            // 获取公钥
            BCECPublicKey bcecPublicKey = (BCECPublicKey) keyPair.getPublic();
            ECPoint ecPoint = bcecPublicKey.getQ();
            byte[] publicKeyByte = ecPoint.getEncoded(false);
            String publicKey = HexUtil.encrypt(publicKeyByte);
            keyMap.put("publicKey", publicKey);

            // 获取私钥
            BCECPrivateKey bcecPrivateKey = (BCECPrivateKey) keyPair.getPrivate();
            BigInteger bigInteger = bcecPrivateKey.getD();
            String privateKey = bigInteger.toString(16);
            keyMap.put("privateKey", privateKey);
        } catch (Exception e) {
            e.fillInStackTrace();
        }

        return keyMap;
    }

    /**
     * SM2公钥加密
     */
    public static String encrypt(String content, String publicKey) {
        byte[] contentByte = content.getBytes(StandardCharsets.UTF_8);

        try {
            SM2Engine sm2Engine = new SM2Engine(MODE_C1C2C3);
            ParametersWithRandom parametersWithRandom = getParametersWithRandom(publicKey);
            sm2Engine.init(true, parametersWithRandom);
            byte[] processBlockByte = sm2Engine.processBlock(contentByte, 0, contentByte.length);
            return HexUtil.encrypt(processBlockByte);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 根据BCECPublicKey获取ParametersWithRandom
     */
    private static ParametersWithRandom getParametersWithRandom(String publicKey) {
        try {
            BCECPublicKey bcecPublicKey = getECPublicKey(publicKey);

            ECParameterSpec ecParameterSpec = bcecPublicKey.getParameters();
            ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(), ecParameterSpec.getG(), ecParameterSpec.getN());
            ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(bcecPublicKey.getQ(), ecDomainParameters);
            return new ParametersWithRandom(ecPublicKeyParameters, new SecureRandom());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 根据公钥获取BCECPublicKey
     */
    private static BCECPublicKey getECPublicKey(String publicKeyStr) {
        try {
            byte[] publicKeyStrByte = HexUtil.getByteByDecrypt(publicKeyStr);

            KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_EC, new BouncyCastleProvider());

            X9ECParameters x9ECParameters = GMNamedCurves.getByName(STDNAME_SM2P);
            ECPoint ecPoint = x9ECParameters.getCurve().decodePoint(publicKeyStrByte);
            ECParameterSpec ecParameterSpec = new ECParameterSpec(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());
            ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(ecPoint, ecParameterSpec);

            PublicKey publicKey = keyFactory.generatePublic(ecPublicKeySpec);
            return (BCECPublicKey) publicKey;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * SM2私钥解密
     */
    public static String decrypt(String encryptStr, String privateKey) {
        try {
            byte[] encryptStrByte = HexUtil.getByteByDecrypt(encryptStr);

            SM2Engine sm2Engine = new SM2Engine(MODE_C1C2C3);
            ECPrivateKeyParameters ecPrivateKeyParameters = getECPrivateKeyParameters(privateKey);
            sm2Engine.init(false, ecPrivateKeyParameters);
            byte[] processBlockByte = sm2Engine.processBlock(encryptStrByte, 0, encryptStrByte.length);
            return new String(processBlockByte, StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取ECPrivateKeyParameters
     */
    private static ECPrivateKeyParameters getECPrivateKeyParameters(String privateKey) {
        try {
            BCECPrivateKey bcecPrivateKey = getBCECPrivateKey(privateKey);

            ECParameterSpec ecParameterSpec = bcecPrivateKey.getParameters();
            ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(), ecParameterSpec.getG(), ecParameterSpec.getN());
            return new ECPrivateKeyParameters(bcecPrivateKey.getD(), ecDomainParameters);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 根据私钥获取BCECPrivateKey
     */
    private static BCECPrivateKey getBCECPrivateKey(String privateKeyStr) {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_EC, new BouncyCastleProvider());
            BigInteger bigInteger = new BigInteger(privateKeyStr, 16);
            X9ECParameters x9ECParameters = GMNamedCurves.getByName(STDNAME_SM2P);
            ECParameterSpec ecParameterSpec = new ECParameterSpec(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());
            ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(bigInteger, ecParameterSpec);
            PrivateKey PrivateKey = keyFactory.generatePrivate(ecPrivateKeySpec);
            return (BCECPrivateKey) PrivateKey;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}